#include <grpcpp/grpcpp.h>

#include <fstream>
#include <iostream>
#include <memory>
#include <string>

#include "hello.grpc.pb.h"

using namespace std;
using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using hello::Greeter;
using hello::HelloRequest;
using hello::HelloResponse;

namespace {
std::shared_ptr<grpc::ChannelCredentials> createTlsCredentials(const std::string& cert, const std::string& key,
                                                               const std::string& ca_cert) {
    auto read_contents = [](const std::string& filename) -> std::string {
        if (filename.empty()) {
            return "";
        }
        std::ifstream fs;
        fs.open(filename);
        if (fs) {
            fs.seekg(0, std::ios::end);
            auto size = fs.tellg();
            std::string buffer(size, '\0');
            fs.seekg(0);
            fs.read(&buffer[0], size);
            return std::move(buffer);
        }
        return "";
    };
    grpc::SslCredentialsOptions opt{read_contents(ca_cert), read_contents(key), read_contents(cert)};
    return ::grpc::SslCredentials(opt);
}
}  // namespace

class GreeterClient {
public:
    GreeterClient() = default;

    Status Connect(const std::string& uri) {
        shared_ptr<grpc::ChannelCredentials> credentials = grpc::InsecureChannelCredentials();
        ::grpc::ChannelArguments args;
        args.SetMaxSendMessageSize(-1);     // max send message size: 2GB
        args.SetMaxReceiveMessageSize(-1);  // max receive message size: 2GB
        uint32_t connectTimeout = 5000;

        channel_ = ::grpc::CreateCustomChannel(uri, credentials, args);
        auto connected =
            channel_->WaitForConnected(std::chrono::system_clock::now() + std::chrono::milliseconds{connectTimeout});
        if (connected) {
            stub_ = Greeter::NewStub(channel_);
            return Status::OK;
        }

        std::string reason = "Failed to connect uri: " + uri;
        return Status(grpc::StatusCode::UNKNOWN, reason);
    }

    Status Disconnect() {
        stub_.reset();
        channel_.reset();
        return Status::OK;
    }

    ~GreeterClient() { Disconnect(); }

    string SayHello(const string& name) {
        // 创建一个客户端上下文
        ClientContext context;

        // 设置请求参数
        HelloRequest request;
        request.set_name(name);
        hello::FieldData field;
        field.set_type(1);
        field.set_field_name("num1");
        request.add_fields_data()->CopyFrom(field);
        field.set_type(2);
        field.set_field_name("num2");
        request.add_fields_data()->CopyFrom(field);

        // 预先分配相应对象
        HelloResponse response;

        // 发送 rpc 并等待相应
        Status status = stub_->SayHello(&context, request, &response);

        // 检查是否出现错误
        if (status.ok()) {
            return response.message();
        } else {
            cerr << "RPC failed, code: " << status.error_code() << ", message: " << status.error_message() << endl;
            return "RPC failed.";
        }
    }

private:
    unique_ptr<Greeter::Stub> stub_;
    shared_ptr<grpc::Channel> channel_;
};

int main() {
    // 服务器地址
    string serverAddress = "0.0.0.0:50051";
    GreeterClient client;

    Status status = client.Connect(serverAddress);
    if (!status.ok()) {
        cout << "Connect failed." << endl;
        return -1;
    }

    string response = client.SayHello("haha");
    cout << response << endl;

    return 0;
}
